#include "StdAfx.h"
#include "Renderer.h"
#include "CG_skel_w_MFC.h"
#include "InitShader.h"
#include "GL\freeglut.h"
#include "PrimMeshModel.h"

#define INDEX(width,x,y,c) (x+y*width)*3+c

Renderer::Renderer() :	m_width(512), m_height(512),flagDrawNormals(false),flagDrawVertexNormals(false),flagDrawBoundingBox(false),
                                                m_regularPen(1,1,0),
						m_activePen(0,1,1),m_currentPen(&m_activePen)

						
{
	InitOpenGLRendering();
	CreateBuffers(512,512);
}
Renderer::Renderer(int width, int height) :	m_width(width), m_height(m_height),flagDrawNormals(false),flagDrawVertexNormals(false),flagDrawBoundingBox(false),
                                                m_regularPen(1,1,0),

						m_activePen(0,1,1),m_currentPen(&m_activePen)
{
	InitOpenGLRendering();
	CreateBuffers(width,height);
}

Renderer::~Renderer(void)
{
	delete this->m_outBuffer; // Heinrich...
	//delete this->m_zbuffer;
}

void Renderer::ReCreateBuffers(int width, int height) {
	delete m_outBuffer;
	CreateBuffers(width,height);
}


void Renderer::CreateBuffers(int width, int height)
{
	m_width=width;
	m_height=height;	
	CreateOpenGLBuffer(); //Do not remove this line.
	m_outBuffer = new float[3*m_width*m_height];
}

void Renderer::SetDemoBuffer()
{
	vec3 v1 = vec3(0,0,0.25);
	vec3 v2 = vec3(0.0,0.25,0.5);
	vec3 v3 = vec3(0,0,0.75);
	this->DrawSingleTriangle(v1,v2,v3);
}

vec2 Renderer::TransformVertex(const vec4 & v){
	vec4 u = this->m_objectMatrix*v;
	return ProjectVertex(u);
}

vec2 Renderer::ProjectVertex(const vec4 & v) {
	vec4 u = this->m_projectionMatrix*v;
	u.perspectiveDivide();
	u = mat4(	vec4(1,0,0,0),
				vec4(0,1,0,0),
				vec4(0,0,0,0),
				vec4(0,0,0,1))*u;
	vec2 w = vec2(u.x,u.y);
	this->TransformToViewport(w);
	return w;	
}

/*
transforms a vector to normalized clipping space..., with perspective division...
*/
vec4 Renderer::Transform2NSC(const vec4 & v) {
	vec4 u = m_projectionMatrix * m_objectMatrix * v;
	u.perspectiveDivide();
	return u;
}

vec2 Renderer::Transform2Screen(const vec4 & v) {
	vec2 w = vec2(v.x,v.y);
	this->TransformToViewport(w);
	return w;	
}

static bool isInsideNCS(const vec4 & v){
	if ((v.x<-1) || (v.x>1) || (v.y < -1) || (v.y > 1) || (v.z<-1) || (v.z > 1)) return false;
	return true;
}


void Renderer::DrawSingleTriangle(const vec4 & v1, const vec4 & v2, const vec4 & v3) {
	vec4 u1, u2, u3;
	u1 = Transform2NSC(v1);
	u2 = Transform2NSC(v2);
	u3 = Transform2NSC(v3);
	vec2 w1, w2, w3;
	w1 = Transform2Screen(u1);
	w2 = Transform2Screen(u2);
	w3 = Transform2Screen(u3);
	if (isInsideNCS(u1)) {
		if (isInsideNCS(u2)) {
			DrawLine(w1,w2);
		}
		if (isInsideNCS(u3)) {
			DrawLine(w1,w3);
		}
	}

	if (isInsideNCS(u2) && isInsideNCS(u3)) {
		DrawLine(w2,w3);
	}
	drawNormalForTriangle(v1,v2,v3,m_normalLength);
}

vec3 Renderer::calculateNormal(const vec4& v1,const vec4& v2,const vec4& v3){

	vec3 v1Real = vec3(v1.x/v1.w,v1.y/v1.w,v1.z/v1.w);
	vec3 v2Real = vec3(v2.x/v2.w,v2.y/v2.w,v2.z/v2.w);
	vec3 v3Real = vec3(v3.x/v3.w,v3.y/v3.w,v3.z/v3.w);
	
	vec3 e1Real = v2Real-v1Real;
	vec3 e2Real = v3Real-v1Real;


	vec3 normalReal = cross(e1Real,e2Real);

	return normalReal;

}

//triangle center is defined as the meeting of the tichons, because it's always inside the triangle. tichons cross eachother in a 2/3 ratio hence the calculation.
vec3 Renderer::calculateTriangleCenter(const vec4& v1,const vec4& v2,const vec4& v3){
	vec3 v1Real = vec3(v1.x/v1.w,v1.y/v1.w,v1.z/v1.w);
	vec3 v2Real = vec3(v2.x/v2.w,v2.y/v2.w,v2.z/v2.w);
	vec3 v3Real = vec3(v3.x/v3.w,v3.y/v3.w,v3.z/v3.w);

	vec3 v1v2 = v2Real-v1Real;

	vec3 tichon = v1Real+(v1v2/2)-v3Real;

	return vec3(v3Real+((tichon*2)/3));
}

void Renderer::drawNormalForTriangle(const vec4& v1,const vec4& v2,const vec4& v3, GLfloat modelDiameter){
	if(flagDrawNormals == false){
		return;
	}
	vec3 normal = normalize(calculateNormal(v1,v2,v3));
	vec3 triangleCenter = calculateTriangleCenter(v1,v2,v3);
	vec3 normalHead = triangleCenter + (modelDiameter/20.0)*(normal);

	vec2 w1 = this->TransformVertex(vec4(triangleCenter));
	vec2 w2 = this->TransformVertex(vec4(normalHead));
	this->DrawLine(w1,w2);
}
void Renderer::DrawPlusAtVertex(const vec4 & v) {
	vec4 u = Transform2NSC(v);
	if (isInsideNCS(u)) {
		vec2 w = Transform2Screen(u);
		vec2 w1 = vec2(w.x,w.y-2);
		vec2 w2 = vec2(w.x,w.y+2);
		vec2 w3 = vec2(w.x-2,w.y);
		vec2 w4 = vec2(w.x+2,w.y);
		DrawLine(w1,w2);
		DrawLine(w3,w4);
	}
}

void Renderer::DrawTriangles(const vector<vec3> * vertices, const vector<vec3> * normals) {
	// we still ignore the normals...
	unsigned int i=0;
	vector<vec3>::const_iterator it = vertices->begin();
	while (i<(vertices->size())) {
		vec4 v1 = vec4(it->x, it->y, it->z, 1);
		it++;
		i++;
		vec4 v2 = vec4(it->x, it->y,it->z,1);
		it++;
		i++;
		vec4 v3 = vec4(it->x, it->y, it->z, 1);
		it++;
		i++;
		// now we got each triangle and we are ready to draw it...
		// cout << i << endl;
		DrawSingleTriangle(v1,v2,v3);
	}
}

void Renderer::drawVertexNormals(const vector<vec3> * vertices, const vector<vec3> * normals)//,GLfloat modelDiameter){
{
	if(flagDrawVertexNormals ==false){
		return;
	}
	unsigned int i=0;
	vector<vec3>::const_iterator vit = vertices->begin();
	vector<vec3>::const_iterator nit = normals->begin();
	while (i<(normals->size())) {
		//cout<<"getting a vn"<<endl;
		vec3 v = vec3(vit->x, vit->y, vit->z);
		vec3 n = vec3(nit->x, nit->y, nit->z);
		//cout<<"normal is "<<n<<endl;
		vit++;
		nit++;
		i++;
		drawVertexNormal(v,n,m_normalLength);
	}
}

void Renderer::TransformToViewport(vec2 & v){
	v.x = (this->m_width-1)/2 + v.x*((this->m_width-1)/2);
	v.y = (this->m_height-1)/2 + v.y*((this->m_height-1)/2);
}

void Renderer::drawVertexNormal(const vec3& v,const vec3& n, GLfloat modelDiameter){

	if ((n.x==0) && (n.y==0) && (n.z==0)){
		return;
	}
	const unsigned int factor =20;

	vec3 normalizedN = normalize((vec3)(n))*(modelDiameter/factor);

	vec3 normalHead = v+normalizedN;
	vec4 normalHead4 = vec4(normalHead,1);
	vec4 v4 = vec4(v,1);

	vec2 w1 = this->TransformVertex(v);
	vec2 w2 = this->TransformVertex(normalHead4);

	this->DrawLine(w1,w2);
}


/////////////////////////////////////////////////////
//OpenGL stuff. Don't touch.

void Renderer::InitOpenGLRendering()
{
	int a = glGetError();
	a = glGetError();
	glGenTextures(1, &gScreenTex);
	a = glGetError();
	glGenVertexArrays(1, &gScreenVtc);
	GLuint buffer;
	glBindVertexArray(gScreenVtc);
	glGenBuffers(1, &buffer);
	const GLfloat vtc[]={
		-1, -1,
		1, -1,
		-1, 1,
		-1, 1,
		1, -1,
		1, 1
	};
	const GLfloat tex[]={
		0,0,
		1,0,
		0,1,
		0,1,
		1,0,
		1,1};
	glBindBuffer(GL_ARRAY_BUFFER, buffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vtc)+sizeof(tex), NULL, GL_STATIC_DRAW);
	glBufferSubData( GL_ARRAY_BUFFER, 0, sizeof(vtc), vtc);
	glBufferSubData( GL_ARRAY_BUFFER, sizeof(vtc), sizeof(tex), tex);

	GLuint program = InitShader( "vshader.glsl", "fshader.glsl" );
	glUseProgram( program );
	GLint  vPosition = glGetAttribLocation( program, "vPosition" );

	glEnableVertexAttribArray( vPosition );
	glVertexAttribPointer( vPosition, 2, GL_FLOAT, GL_FALSE, 0,
		0 );

	GLint  vTexCoord = glGetAttribLocation( program, "vTexCoord" );
	glEnableVertexAttribArray( vTexCoord );
	glVertexAttribPointer( vTexCoord, 2, GL_FLOAT, GL_FALSE, 0,
		(GLvoid *) sizeof(vtc) );
	glUniform1i( glGetUniformLocation(program, "texture"), 0 );
	a = glGetError();
}

void Renderer::CreateOpenGLBuffer()
{
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, gScreenTex);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, m_width, m_height, 0, GL_RGB, GL_FLOAT, NULL);
	glViewport(0, 0, m_width, m_height);
}

void Renderer::SwapBuffers()
{

	int a = glGetError();
	glActiveTexture(GL_TEXTURE0);
	a = glGetError();
	glBindTexture(GL_TEXTURE_2D, gScreenTex);
	a = glGetError();
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_width, m_height, GL_RGB, GL_FLOAT, m_outBuffer);
	glGenerateMipmap(GL_TEXTURE_2D);
	a = glGetError();

	glBindVertexArray(gScreenVtc);
	a = glGetError();
	glDrawArrays(GL_TRIANGLES, 0, 6);
	a = glGetError();
	glutSwapBuffers();
	a = glGetError();
}

void Renderer::PlotPixel(const int & x, const int & y) {
	if ((x>=m_width) || (x<0)) return;
	if ((y>=m_height) || (y<0)) return;
	int idx1, idx2, idx3;
	idx1 = INDEX(m_width,x,y,0);
	idx2 = INDEX(m_width,x,y,1);
	idx3 = INDEX(m_width,x,y,2);
	this->m_outBuffer[idx1]=m_currentPen->getRed();
	this->m_outBuffer[idx2]=m_currentPen->getGreen();
	this->m_outBuffer[idx3]=m_currentPen->getBlue();;
}

#define SWAP(X,Y) X^=Y; Y^=X; X^=Y;

void Renderer::DrawLine(const vec2 & v1,const vec2 & v2) {
	if (v1.x>v2.x) {
		this->DrawLine(v2,v1);
		return;
	}
	int x = floor(v1.x+0.5);
	int y = floor(v1.y+0.5);
	int yGoal = floor(v2.y+0.5);
	int xGoal = floor(v2.x+0.5);
	int dx = xGoal - x;
	int dy = yGoal - y;
	int yStep, xStep=1;
	int primaryStep, secondaryStep;
	int * primaryVar;
	int * secondaryVar;
	if (dy<0) {
		dy*=-1;
		yStep = -1;
	}
	else {
		yStep = 1;
	}
	if (dy>dx) {
		SWAP(dy,dx);
		primaryVar = &y;
		primaryStep = yStep;
		secondaryVar = &x;
		secondaryStep = xStep;
	}
	else {
		primaryVar = &x;
		primaryStep = xStep;
		secondaryVar = &y;
		secondaryStep = yStep;
	}
	int steps = dx+1;
	int d = 2*dy-dx;
	int de = 2*dy;
	int dne = 2*(dy-dx);
	while (steps>0) {
		PlotPixel(x,y);
		if (d<0) {
			d+=de;
		}
		else {
			d+=dne;
			*secondaryVar=(*secondaryVar)+secondaryStep;
		}
		//increment primary variable...
		*primaryVar = (*primaryVar)+primaryStep;
		steps--;
	}
}

void Renderer::SetProjection(const mat4 & projMat) {
	this->m_projectionMatrix = projMat;
}

void Renderer::SetCameraTransform(const mat4 & camMat) {
	this->m_cameraMatrix = camMat;
}

void Renderer::SetObjectMatrices(const mat4 & oTransform, const mat3 & nTransform) {
	this->m_objectMatrix = oTransform;
	this->m_normalMatrix = nTransform;
}
void Renderer::drawBoundingBox(vec3 boundingBoxMin, vec3 boundingBoxMax){
	if(flagDrawBoundingBox == false){
		return;
	}

	Ark ark = Ark(this);
	ark.build(boundingBoxMin,boundingBoxMax);

	//close,far,top,bottom,left,right
	vec2 BLC = this->TransformVertex(ark.vBLC);
	vec2 BLF = this->TransformVertex(ark.vBLF);
	vec2 BRC = this->TransformVertex(ark.vBRC);
	vec2 BRF = this->TransformVertex(ark.vBRF);
	vec2 TLC = this->TransformVertex(ark.vTLC);
	vec2 TLF = this->TransformVertex(ark.vTLF);
	vec2 TRC = this->TransformVertex(ark.vTRC);
	vec2 TRF = this->TransformVertex(ark.vTRF);
	
	this->DrawLine(BLC,BLF);	
	this->DrawLine(BLC,BRC);
	this->DrawLine(BLC,TLC);
	this->DrawLine(TRF,BRF);
	this->DrawLine(TRF,TLF);
	this->DrawLine(TRF,TRC);
	this->DrawLine(TLF,TLC);
	this->DrawLine(TLF,BLF);
	this->DrawLine(BLF,BRF);
	this->DrawLine(BRC,BRF);
	this->DrawLine(TRC,BRC);
	this->DrawLine(TRC,TLC);
}